﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Threading;
using Windows8Accelerator.Core.Xaml.Controls;
using Windows8Accelerator.Core.Xaml.Helpers;
using Windows8Accelerator.Core.Xaml.Storage;

namespace Windows8Accelerator.Core.Xaml.Caching
{
    public static class CacheManager
    {
        static CacheManager()
        {
            CacheExpiration = new TimeSpan(30, 0, 0, 0);
            InitializeAsync();
        }

        private static async void InitializeAsync()
        {
            foreach (var file in await StorageHelperBase.Instance.GetFilesAsync(URI_CACHE_FOLDER, StorageLocation.Local))
            {
                MemoryUriCache[file.Name] = null;
            }

            //await Task.Delay(new TimeSpan(0, 2, 0));

            await DeleteOldFiles(URI_CACHE_FOLDER, StorageLocation.Local);
            await DeleteOldFiles(CACHE_FOLDER, StorageLocation.Local);
        }

        private static async Task DeleteOldFiles(string directory, StorageLocation location)
        {
            var minDate = DateTime.Now - CacheExpiration;
            foreach (var file in await StorageHelperBase.Instance.GetFilesAsync(directory + "\\", location))
            {

                if (file.DateCreated < minDate)
                {
                    MemoryUriCache.Remove(file.Name);
                    await StorageHelperBase.Instance.DeleteFilesAsync(file.Path, StorageLocation.Path);
                }
            }
        }

        public static TimeSpan CacheExpiration { get; set; }

        private const string CACHE_FOLDER = @"__cache__";
        private const string URI_CACHE_FOLDER = @"__uricache__";
        private const string CACHE_EXTENSION = @".bin";
        private const string URL_FORMAT = "ms-appdata:///local/{0}/{1}";

        private static Dictionary<string, CachedObject> MemoryCache = new Dictionary<string, CachedObject>();
        private static Dictionary<string, object> MemoryUriCache = new Dictionary<string, object>();

        public static Uri GetOrRetrieveUri(string url)
        {
            if (string.IsNullOrEmpty(url))
                return null;
            return GetOrRetrieveUri(new Uri(url));
        }

        public static Uri GetOrRetrieveUri(Uri uri)
        {
            var cacheKey = FixCacheKey(uri.ToString());

            if (MemoryUriCache.ContainsKey(cacheKey))
                return new Uri(string.Format(URL_FORMAT, URI_CACHE_FOLDER, cacheKey));

            RetrieveUriAsync(uri, cacheKey);

            return uri;
        }

        public static async Task<Uri> GetOrRetrieveUriAsync(string url)
        {
            return await GetOrRetrieveUriAsync(new Uri(url));
        }

        public static async Task<Uri> GetOrRetrieveUriAsync(Uri uri)
        {
            var cacheKey = FixCacheKey(uri.ToString());

            if (MemoryUriCache.ContainsKey(cacheKey))
                return new Uri(string.Format(URL_FORMAT, URI_CACHE_FOLDER, cacheKey));

            await RetrieveUriAsync(uri, cacheKey);

            if (MemoryUriCache.ContainsKey(cacheKey))
                return new Uri(string.Format(URL_FORMAT, URI_CACHE_FOLDER, cacheKey));

            return uri;
        }

        private static string FixCacheKey(string p)
        {
            if (p.StartsWith("_"))
                return p;
            p = Regex.Replace(p, @"(\\|/|:|\?|\*|""|<|>|\|\s)", "_");
            p = p.GetHashCode().ToString("_00000000_;_00000000_") + p.Substring(Math.Max(p.Length - 100, 0));
            return p;
        }

        private static async Task RetrieveUriAsync(Uri uri, string cacheKey)
        {
            cacheKey = FixCacheKey(cacheKey);

            using (var response = await WebHelperBase.Instance.GetStreamAsync(uri))
            {
                try
                {
                    await StorageHelperBase.Instance.CreateFileAsync(Path.Combine(URI_CACHE_FOLDER, cacheKey), StorageLocation.Local);
                    using (var stream = await StorageHelperBase.Instance.OpenFileWriteAsync(Path.Combine(URI_CACHE_FOLDER, cacheKey), StorageLocation.Local, true))
                    {
                        await response.CopyToAsync(stream);
                        stream.Flush();
                        MemoryUriCache[cacheKey] = null;
                    }
                }
                catch { }
            }
        }

        public static async Task<CachedObject<T>> UpdateAsync<T>(string cacheKey, Func<Task<T>> retriveFunction)
            where T : class
        {
            //using (ProgressBarManager.NewWork())
            {
                CachedObject<T> cachedObject = null;
                try
                {
                    cacheKey = FixCacheKey(cacheKey);

                    var obj = await retriveFunction();
                    if (obj != null)
                    {
                        var retrievedCachedObject = new CachedObject()
                        {
                            CacheDate = DateTime.Now,
                            Object = obj,
                        };
                        await ObjectPersister.SaveAsync(Path.Combine(CACHE_FOLDER, cacheKey) + CACHE_EXTENSION, StorageLocation.Local, retrievedCachedObject, ObjectPersisterFormat.Binary);
                        if (retrievedCachedObject != null)
                        {
                            lock (MemoryCache)
                            {
                                MemoryCache[cacheKey] = retrievedCachedObject;
                            }
                            cachedObject = new CachedObject<T>(retrievedCachedObject, ObjectOrigin.RetrievalFunction);
                        }
                    }
                    else
                    {
                        cachedObject = new CachedObject<T>(ObjectOrigin.Error);
                    }
                }
                catch (Exception exception)
                {
                    cachedObject = new CachedObject<T>(exception);
                }

                return cachedObject;
            }
        }

        public static GetAndRetrieveResult<T> GetAndRetrieveAsync<T>(string cacheKey, Func<Task<T>> retriveFunction, TimeSpan? maxAge = null)
            where T : class
        {
            return new GetAndRetrieveResult<T>() { 
                CachedObjectTask = GetAsync<T>(cacheKey),
                RetrievedObjectTask = GetOrRetrieveAsync<T>(cacheKey, retriveFunction, maxAge),
            };
        }

        public static async Task<CachedObject<T>> GetAsync<T>(string cacheKey)
            where T : class
        {
            //using (ProgressBarManager.NewWork())
            {
                CachedObject<T> cachedObject = null;
                try
                {
                    cacheKey = FixCacheKey(cacheKey);

                    lock (MemoryCache)
                    {
                        if (MemoryCache.ContainsKey(cacheKey))
                            cachedObject = new CachedObject<T>(MemoryCache[cacheKey], ObjectOrigin.MemoryCache);
                    }

                    try
                    {
                        var diskCachedObject = await ObjectPersister.LoadAsync<CachedObject>(Path.Combine(CACHE_FOLDER, cacheKey) + CACHE_EXTENSION, StorageLocation.Local, ObjectPersisterFormat.Binary);
                        if (diskCachedObject != null)
                        {
                            lock (MemoryCache)
                            {
                                MemoryCache[cacheKey] = diskCachedObject;
                            }
                            cachedObject = new CachedObject<T>(diskCachedObject, ObjectOrigin.FileCache);
                        }
                    }
                    catch { }
                }
                catch (Exception exception)
                {
                    cachedObject = new CachedObject<T>(exception);
                }
                if (cachedObject == null)
                {
                    cachedObject = new CachedObject<T>(ObjectOrigin.NotFound);
                }
                return cachedObject;
            }
        }

        public static async Task ClearAsync(string cacheKey)
        {
            //using (ProgressBarManager.NewWork())
            {
                cacheKey = FixCacheKey(cacheKey);

                lock (MemoryCache)
                {
                    if (MemoryCache.ContainsKey(cacheKey))
                        MemoryCache.Remove(cacheKey);
                }

                try
                {
                    await ObjectPersister.DeleteAsync(Path.Combine(CACHE_FOLDER, cacheKey) + CACHE_EXTENSION, StorageLocation.Local);
                }
                catch { }
            }
        }

        public static async Task<CachedObject<T>> GetOrRetrieveAsync<T>(string cacheKey, Func<Task<T>> retriveFunction, TimeSpan? maxAge = null)
            where T : class
        {
            CachedObject<T> cachedObject = null;
            try
            {
                cachedObject = await GetAsync<T>(cacheKey);
                if ((cachedObject.Origin == ObjectOrigin.FileCache ||
                    cachedObject.Origin == ObjectOrigin.MemoryCache) &&
                    (!maxAge.HasValue || DateTime.Now - cachedObject.CacheDate < maxAge.Value))
                {
                    return cachedObject;
                }

                cachedObject = await UpdateAsync<T>(cacheKey, retriveFunction);
            }
            catch (Exception exception)
            {
                cachedObject = new CachedObject<T>(exception);
            }

            return cachedObject;
        }
    }
}
